package com.glodon.paas.account.sdk.oauth1.web;

import com.glodon.paas.account.api.oauth1.AccessValidationRequest;
import com.glodon.paas.account.api.oauth1.AccessValidationResponse;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.json.JSONConfiguration;
import net.oauth.OAuthMessage;
import net.oauth.server.OAuthServlet;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.MediaType;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * A web filter protecting OAuth 1.0 based web APIs.
 *
 * @author Don Li
 */
public class OAuthFilter implements Filter {

    private static final String AUTHENTICATE_HEADER = "WWW-Authenticate";
    private static final String OAUTH_VALIDATION_RESPONSE = "oauth-validation-response";

    private String validationUrl;
    private Client restClient;

    /**
     * Get the AccessValidationResponse from http request.
     *
     * @param request http servlet request
     * @return AccessValidationResponse
     */
    public static AccessValidationResponse getValidationResponse(HttpServletRequest request) {
        Object response = request.getAttribute(OAUTH_VALIDATION_RESPONSE);
        if (response instanceof AccessValidationResponse) {
            return (AccessValidationResponse) response;
        }
        throw new RuntimeException("unauthorized request");
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // set token validation url
        validationUrl = filterConfig.getInitParameter("tokenValidationUrl");
        if (validationUrl == null) {
            loadPropertiesFromFile();
            if (validationUrl == null) {
                throw new IllegalArgumentException("init param tokenValidationUrl is missing");
            }
        }
        // create a reusable rest client
        ClientConfig clientConfig = new DefaultClientConfig();
        clientConfig.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE);
        restClient = Client.create(clientConfig);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        AccessValidationResponse respMessage = validateRequest(req);
        if (respMessage.isAuthorized()) {
            req.setAttribute(OAUTH_VALIDATION_RESPONSE, respMessage);
            chain.doFilter(request, response);
        } else {
            sendErrorResponse(req, resp, 401, "unauthorized access");
        }
    }

    @Override
    public void destroy() {
        restClient.destroy();
    }

    public void setValidationUrl(String validationUrl) {
        this.validationUrl = validationUrl;
    }

    private void loadPropertiesFromFile() throws ServletException {
        String path = System.getProperty("user.home") + "/paas.properties";
        File file = new File(path);
        if (!file.exists()) {
            path = "default.properties";
        }
        Properties props = new Properties();
        InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
        if (inputStream == null) {
            throw new ServletException("properties file cannot be load");
        } else {
            try {
                props.load(inputStream);
                validationUrl = resolveProperty(props.getProperty("oauth.validation.url"), props);
            } catch (IOException e) {
                throw new ServletException("properties file cannot be load");
            } finally {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private String resolveProperty(String property, Properties props) {
        String result = property;
        for (int i = 0; i < property.length() - 2; i++) {
            if (property.charAt(i) == '$' && property.charAt(i + 1) == '{') {
                for (int j = i + 2; j < property.length(); j++) {
                    if (property.charAt(j) == '}') {
                        String p = property.substring(i, j + 1);
                        String t = property.substring(i + 2, j);
                        result = result.replace(p, props.getProperty(t));
                    }
                }
            }
        }
        return result;
    }

    private AccessValidationResponse validateRequest(HttpServletRequest request) {
        OAuthMessage requestMessage = OAuthServlet.getMessage(request, null);
        WebResource resource = restClient.resource(validationUrl);
        return resource.type(MediaType.APPLICATION_JSON).post(AccessValidationResponse.class, new AccessValidationRequest(requestMessage, request));
    }

    private void sendErrorResponse(HttpServletRequest req, HttpServletResponse resp, int statusCode, String message) throws IOException {
        String realm = (req.isSecure()) ? "https://" : "http://";
        realm += req.getServerName();
        String headerValue = "OAuth";
        headerValue += (" realm=\"" + realm + "\"");
        resp.getWriter().append(message);
        resp.setStatus(statusCode);
        resp.setHeader(AUTHENTICATE_HEADER, headerValue);
    }
}
